#ifndef _tenacitas__balance_line_h_
#define _tenacitas__balance_line_h_

// =============================================================================
/** @file */

// =============================================================================
/** todo */

// =============================================================================
//                                 C++ Headers
#include <memory>
#include <list>
#include <utility>
#include <functional>
#include <iostream>	

// =============================================================================
//                                 3rds Headers


// =============================================================================
//                                 Our Headers

#include <tenacitas.exception/exception.h>

// =============================================================================
//                                 Namespaces
using namespace std;


// =============================================================================
//                                 Macro Contants
/** */


// =============================================================================
//                                 Macro Commands
/** */


/** namespace of the organization */
namespace tenacitas {
    /** namespace for the project */
    namespace balance_line {

        // =========================== Typedefs ===========================


        // ============================ Pre-Declarations ==================

        // ============================ Attributes ========================
        /** */

        // ============================ Functions =========================
        /** */

        // ============================ Classes ===========================

        /** Merges a set of data sources into one


            @tparam t_source is the type which contais a collection of data to be
            merged. It must implement:
            @code
            typedef ptr

            typedef data

            typedef data_ptr

            data_ptr read(); which must data_ptr( ) if there is no more data to be
            read 
            @endcode

            @tparam t_cmp is a comparision functor that must implement
            @code
            bool operator ( )( const t_source::data & p_d1,
            const t_source::data & p_d2 ), which should return
            true if p_d1 is, in any sense, smaller than p_d2
            @endcode

            @nosubgrouping
        */
        template <typename t_source,
                  typename t_cmp = std::less<typename t_source::data> >
        class merger {

        public:

            // --- merger - Public Internal Types ------------------------

            /** */
            typedef std::shared_ptr<merger> ptr;

            /** */
            typedef t_source source;

            /** */
            typedef typename source::ptr source_ptr;

            /** */
            typedef typename source::data data;

            /** */
            typedef typename source::data_ptr data_ptr;

            /** */
            typedef t_cmp cmp;


            // --- merger - Public Constructors --------------------------

            /** Creates a reference to a @p merger object */
            static ptr create( ) {

                ptr l_rc;
                try {
                    l_rc = ptr( new merger( ) );
                }
                catch( tenacitas::exception::exception & l_ex ) {
#ifdef DEBUG
                    std::cout << l_ex.what( ) << std::endl;
#endif
                    throw l_ex;
                }
                catch( std::exception & l_ex ) {
                    tenacitas::exception::exception l_tenacitas_ex =
                        make_tenacitas_exception( l_ex.what( ) );
#ifdef DEBUG
                    std::cout << l_tenacitas_ex.what( ) << std::endl;
#endif
                    throw l_tenacitas_ex;
                }
                return l_rc;
            }

            /**
             */
            merger( const merger & ) = delete;

            /**
             */
            merger( merger && ) = delete;

            /** Destructor */
            ~merger( ) {}            

            // --- merger - Public Accessors -----------------------------

            /**
               @param  p_source is one more data source to be merged
            */
            void add( source_ptr p_source ) {
#ifdef DEBUG
                std::cout << "adding source '" << p_source->name( ) << "'"
                          << std::endl; 
#endif

                try {

                    // we only add this source if it contains data 
                    data_ptr l_data = p_source->read();
                    if ( l_data ) {
                        // and this make it easier to calculate the smallest data
                        // later

#ifdef DEBUG
                        std::cout <<  "source has data: " << *l_data << std::endl;
#endif
                    
                        m_sources_data.push_back( std::make_pair( p_source,
                                                                  l_data ) ); 
                    }
                }                
                catch( tenacitas::exception::exception & l_ex ) {
#ifdef DEBUG
                    std::cout << l_ex.what( ) << std::endl;
#endif
                    throw l_ex;
                }
                catch( std::exception & l_ex ) {
                    tenacitas::exception::exception l_tenacitas_ex =
                        make_tenacitas_exception( l_ex.what( ) );
#ifdef DEBUG
                    std::cout << l_tenacitas_ex.what( ) << std::endl;
#endif
                    throw l_tenacitas_ex;
                }
            }

            // --- merger - Public Processors ----------------------------

            /** Starts merging the data sources

                @tparam t_output is the class that will handled each data merged
                from the sources. It must implement:
                @code
                void add( data_ptr )
                @endcode

                @param  p_output will handled each data merged from the sources
            */
            template <typename t_output>
            void start( t_output & p_output ) {

#ifdef DEBUG
                std::cout << "starting" << std::endl;
#endif
                                
                try {
                    while( true ) {

                        if ( m_sources_data.empty( ) ) {
#ifdef DEBUG
                            std::cout << "no data source to be merged"
                                      << std::endl;
#endif
                            break;
                        }

#ifdef DEBUG
                        std::cout << "still data source to be merged"
                                  << std::endl;
#endif
                    
                        data_ptr l_smallest = calculate_smallest_data();
                        if ( l_smallest ) {

#ifdef DEBUG
                            std::cout << "a new smallest found: '" << *l_smallest
                                      << std::endl;  
#endif
                            p_output.add( l_smallest );
                        }

                        read_data( );
                    }
                }
                catch( tenacitas::exception::exception & l_ex ) {
#ifdef DEBUG
                    std::cout << l_ex.what( ) << std::endl;
#endif
                    throw l_ex;
                }
                catch( std::exception & l_ex ) {
                    tenacitas::exception::exception l_tenacitas_ex =
                        make_tenacitas_exception( l_ex.what( ) );
#ifdef DEBUG
                    std::cout << l_tenacitas_ex.what( ) << std::endl;
#endif
                    throw l_tenacitas_ex;
                }
            }            

            // --- merger - Public Operators -----------------------------

            /** not-allowed */
            merger & operator =( const merger & ) = delete;

            /** not-allowed */
            merger & operator =( merger && ) = delete;

            /** not-allowed */
            bool operator ==( const merger & ) const = delete;

            /** not-allowed */
            bool operator !=( const merger & ) const = delete;

            // --- merger - Public Attributes ----------------------------

        private:

            // --- merger - Private Internal Types -----------------------

            /**
             */
            typedef std::pair<source_ptr, data_ptr> source_and_data;

            /**
             */
            typedef list<source_and_data> sources_and_data;

            /** */
            typedef typename sources_and_data::iterator iterator;

            /** */
            typedef typename sources_and_data::const_iterator const_iterator;
            

            // --- merger - Private Constructors -------------------------

            /** Constructor */
            merger()
                : m_sources_data(),
                  m_smallest( m_sources_data.end() ) {}

            // --- merger - Private Processors ---------------------------

            /** Updates the data from the source that had the last smallest data */
            void read_data() {


                // reading a new data from the source that had the previous
                // smallest data

                try {
                    
                    m_smallest->second = m_smallest->first->read();

                    if ( !m_smallest->second ) {
                        m_sources_data.erase( m_smallest );
                        m_smallest = m_sources_data.end( );
                    }
                    
                }
                catch( tenacitas::exception::exception & l_ex ) {
#ifdef DEBUG
                    std::cout << l_ex.what( ) << std::endl;
#endif
                    throw l_ex;
                }
                catch( std::exception & l_ex ) {
                    tenacitas::exception::exception l_tenacitas_ex =
                        make_tenacitas_exception( l_ex.what( ) );
#ifdef DEBUG
                    std::cout << l_tenacitas_ex.what( ) << std::endl;
#endif
                    throw l_tenacitas_ex;
                }
            }
            

            /** Calculates the smallest data among the data sources */
            data_ptr calculate_smallest_data() {

                m_smallest = m_sources_data.begin();

                try {
                    
                    iterator l_end = m_sources_data.end();
                    iterator l_ite = ++( m_sources_data.begin() );
                
                    for( ; l_ite != l_end; ++l_ite ) {

                        const data & l_d1 = *l_ite->second;
                        const data & l_d2 = *m_smallest->second;
                        
                        if ( cmp( )( l_d1, l_d2 ) ) {
                            m_smallest = l_ite;
                        }
                    }
                }
                catch( tenacitas::exception::exception & l_ex ) {
#ifdef DEBUG
                    std::cout << l_ex.what( ) << std::endl;
#endif
                    throw l_ex;

                }
                catch( std::exception & l_ex ) {
                    tenacitas::exception::exception l_tenacitas_ex =
                        make_tenacitas_exception( l_ex.what( ) );
#ifdef DEBUG
                    std::cout << l_tenacitas_ex.what( ) << std::endl;
#endif
                    throw l_tenacitas_ex;
                }

                return m_smallest->second;
            }

            // --- merger - Private Attributes ---------------------------

            /**
             */
            sources_and_data m_sources_data;

            /**
             */
            iterator m_smallest;

        };
    }
}

// =============================================================================
//                     I M P L E M E N T A T I O N
// =============================================================================

using namespace tenacitas::balance_line;


#endif
